//---------------------------------------------------------------------
// File: BizUnit.cs
// 
// Summary: 
//
//---------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------

namespace Microsoft.Services.BizTalkApplicationFramework.BizUnit
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Collections;
    using System.Reflection;
    using System.Threading;

    /// <summary>
    /// BizUnit test framework for the rapid development of automated test cases.
    /// </summary>
    /// 
    /// <remarks>
    /// The following example demonstrates how to create and call BizUnit:
    /// 
    /// <code escaped="true">
    /// namespace WoodgroveBank.BVTs
    ///	{
    ///     using System;
    ///     using NUnit.Framework;
    ///     using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
    ///
    ///     // This is an example of calling BizUnit from NUnit...
    ///     [TestFixture]
    ///     public class SmokeTests
    ///     {
    ///         [Test]
    ///         public void Test_01_Adapter_MSMQ()
    ///         {
    ///             BizUnit bizUnit = new BizUnit(@".\TestCases\Test_01_Adapter_MSMQ.xml");
    ///             bizUnit.RunTest();
    ///         }
    ///     }
    /// }		
    ///	</code>
    ///	
    ///	<para>
    ///	Test cases are defined with three stages:
    ///	<para>1. TestSetup - used to setup the conditions ready to execute the test</para>
    ///	<para>2. TestExecution - the main execution stage of the test</para>
    ///	<para>3: TestCleanup - the final stage is always executed regardless of whether the test passes 
    ///	or fails in order to leave the system in the state prior to executing the test</para>
    ///	</para>
    ///	
    ///	<para>
    ///	The following illustrates the structure of a test case:
    /// <code escaped="true">
    ///	<TestCase testName="FILE_Test_01">
    ///	
    ///		<TestSetup>
    ///			<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.ExecuteCommandStep">
    ///             ...
    ///			</TestStep>
    ///		</TestSetup>
    ///		
    ///		<TestExecution>
    ///			<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.HttpRequestResponseStep" runConcurrently="true">
    ///             ...
    ///			</TestStep>
    ///				
    ///			<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.FileValidateStep">
    ///             ...
    ///			</TestStep>
    ///					
    ///		</TestExecution>
    ///		
    ///		<!-- Test cleanup: test cases should always leave the system in the state they found it -->
    ///		<TestCleanup>
    ///			<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.FileDeleteStep">
    ///             ...
    ///			</TestStep>
    ///		</TestCleanup>	
    ///	
    ///	</TestCase>
    ///	</code>
    ///	
    ///	<list type="table">
    ///		<listheader>
    ///			<term>Tag</term>
    ///			<description>Description</description>
    ///		</listheader>
    ///		<item>
    ///			<term>TestCase/testName</term>
    ///			<description>The name of the test case being executed</description>
    ///		</item>
    ///		<item>
    ///			<term>TestSetup</term>
    ///			<description>The setup stage of the test case, containing zero or more test steps</description>
    ///		</item>
    ///		<item>
    ///			<term>TestExecution</term>
    ///			<description>The execution stage of the test case, containing zero or more test steps</description>
    ///		</item>
    ///		<item>
    ///			<term>TestExecution/runConcurrently</term>
    ///			<description>If runConcurrently is set to true the test step will be executed concurrently, meaning that 
    ///			subsequent test steps will be started immediately after this test step has started. BizUnit will 
    ///			wait for all of the outstanding test steps executing in concurrent mode before running the TestCleanup 
    ///			stage.</description>
    ///		</item>
    ///		<item>
    ///			<term>TestCleanup</term>
    ///			<description>The clenaup stage should return the system to the state prior to executing the test, for
    ///			example, if messages were written to various MSMQ queue's, the TestCleanup stage should ensure that
    ///			all of those queues are empty.</description>
    ///		</item>
    ///	</list>
    ///	</para>
    /// <para>
    /// Wild cards contained in configuration will be replaced if the test steps extract the configuration using the 
    /// context helper class.
    /// E.g. For the test step configuration below:
    /// <code escaped="true">
    /// <TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.FileCreateStep">
    ///     <SourcePath>..\..\..\TestData\InDoc1.xml</SourcePath>
    ///     <CreationPath>..\..\..\Rec_03\TransactionId_%Guid%_%ServerName%.xml</CreationPath>
    ///	</TestStep>
    /// </code>
    /// CreationPath becomes "..\..\..\Rec_03\TransactionId_12345678-D6AB-4aa9-A772-938972E3FD51_ZEUS001.xml"
    /// </para>
    /// </remarks>
    public class BizUnit
    {
        XmlNodeList setupSteps;
        XmlNodeList executeSteps;
        XmlNodeList teardownSteps;
        string testName = "Unknown";
        Exception executionException;
        Context context;
        internal Logger logger;
        Queue completedConcurrentSteps = new Queue();
        int inflightQueueDepth;
        TestGroupPhase testGroupPhase = TestGroupPhase.Unknown;

        /// <summary>The TestGroupPhase enumeration is used to specify the setup and teardown phases of a group of test cases. For example when using the NUnit attributes: [TestFixtureSetUp] or [TestFixtureTearDown].
        /// </summary>
        public enum TestGroupPhase
        {
            ///<summary>The start of the test group setup</summary>
            TestGroupSetup,
            ///<summary>The end of the test group setup</summary>
            TestGroupTearDown,
            ///<summary>Undefined</summary>
            Unknown
        };

        /// <summary>
        /// BizUnit constructor.
        /// </summary>
        /// <param name="configFile">The path of the test case file, maybe a relavtive path.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///		[Test]
        ///		public void Test_01_Adapter_MSMQ()
        ///		{
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_01_Adapter_MSMQ.xml");
        ///			bizUnit.RunTest();
        ///		}
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(string configFile)
        {
            ArgumentValidation.CheckForNullReference(configFile, "configFile");

            LoadXmlFromFileAndInit(configFile, TestGroupPhase.Unknown, null);
        }

        /// <summary>
        /// BizUnit constructor.
        /// </summary>
        /// <param name="configFile">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="ctx">The BizUnit context object may be flowed from an previous test case.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///     Context ctx;
        /// 
        ///		[Test]
        ///		public void Test_02_Adapter_MSMQ()
        ///		{
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_01_Adapter_MSMQ.xml", this.ctx);
        ///			bizUnit.RunTest();
        ///		}
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(string configFile, Context ctx)
        {
            ArgumentValidation.CheckForNullReference(configFile, "configFile");
            ArgumentValidation.CheckForNullReference(ctx, "ctx");

            LoadXmlFromFileAndInit(configFile, TestGroupPhase.Unknown, ctx);
        }

        /// <summary>
        /// BizUnit constructor.
        /// </summary>
        /// <param name="configStream">The path of the test case file, maybe a relavtive path.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///		[Test]
        ///		public void Test_03_Adapter_MSMQ()
        ///		{
        ///         // The test case is an embeded resource...
        ///			BizUnit bizUnit = new BizUnit(Assembly.GetExecutingAssembly().GetManifestResourceStream("BizUnit.SampleTests.BizUnitFunctionalTests.Test_04_MQSeriesTest.xml"));
        ///			bizUnit.RunTest();
        ///		}
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(Stream configStream)
        {
            ArgumentValidation.CheckForNullReference(configStream, "configStream");

            LoadXmlFromStreamAndInit(configStream, TestGroupPhase.Unknown, null);
        }

        /// <summary>
        /// BizUnit constructor.
        /// </summary>
        /// <param name="configStream">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="ctx">The BizUnit context object may be flowed from an previous test case.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///     Context ctx;
        /// 
        ///		[Test]
        ///		public void Test_04_Adapter_MSMQ()
        ///		{
        ///         // The test case is an embeded resource...
        ///			BizUnit bizUnit = new BizUnit(Assembly.GetExecutingAssembly().GetManifestResourceStream("BizUnit.SampleTests.BizUnitFunctionalTests.Test_04_MQSeriesTest.xml"), this.ctx);
        ///			bizUnit.RunTest();
        ///		}
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(Stream configStream, Context ctx)
        {
            ArgumentValidation.CheckForNullReference(configStream, "configStream");
            ArgumentValidation.CheckForNullReference(ctx, "ctx");

            LoadXmlFromStreamAndInit(configStream, TestGroupPhase.Unknown, ctx);
        }

        /// <summary>
        /// BizUnit constructor for the setup and teardown of a test group.
        /// </summary>
        /// <param name="configFile">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="testGroupPhase">The test group phase (TestGroupPhase.TestGroupSetup|TestGroupPhase.TestGroupTearDown). This 
        /// constructor is used during the initialization or termination of a group of test cases, for example when using the NUnit attributes: [TestFixtureSetUp] or [TestFixtureTearDown].</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///		[TestFixtureSetUp]
        ///		public void Test_Group_Setup()
        ///		{
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_Group_Setup.xml", BizUnit.TestGroupPhase.TestGroupSetup);
        ///			bizUnit.RunTest();
        ///		}
        ///		
        ///		...
        ///		
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(string configFile, TestGroupPhase testGroupPhase)
        {
            ArgumentValidation.CheckForNullReference(configFile, "configFile");
            ArgumentValidation.CheckForNullReference(testGroupPhase, "testGroupPhase");

            LoadXmlFromFileAndInit(configFile, testGroupPhase, null);
        }

        /// <summary>
        /// BizUnit constructor for the setup and teardown of a test group.
        /// </summary>
        /// <param name="configFile">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="testGroupPhase">The test group phase (TestGroupPhase.TestGroupSetup|TestGroupPhase.TestGroupTearDown). This 
        /// constructor is used during the initialization or termination of a group of test cases, for example when using the NUnit attributes: [TestFixtureSetUp] or [TestFixtureTearDown].</param>
        /// <param name="ctx">The BizUnit context object may be flowed from an previous test case.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        /// 
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///     Context ctx;
        /// 
        ///		[TestFixtureSetUp]
        ///		public void Test_Group_Setup()
        ///		{
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_Group_Setup.xml", BizUnit.TestGroupPhase.TestGroupSetup, this.ctx);
        ///			bizUnit.RunTest();
        ///		}
        ///		
        ///		...
        ///		
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(string configFile, TestGroupPhase testGroupPhase, Context ctx)
        {
            ArgumentValidation.CheckForNullReference(configFile, "configFile");
            ArgumentValidation.CheckForNullReference(testGroupPhase, "testGroupPhase");
            ArgumentValidation.CheckForNullReference(ctx, "ctx");

            LoadXmlFromFileAndInit(configFile, testGroupPhase, ctx);
        }

        /// <summary>
        /// BizUnit constructor for the setup and teardown of a test group.
        /// </summary>
        /// <param name="configStream">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="testGroupPhase">The test group phase (TestGroupPhase.TestGroupSetup|TestGroupPhase.TestGroupTearDown). This 
        /// constructor is used during the initialization or termination of a group of test cases, for example when using the NUnit attributes: [TestFixtureSetUp] or [TestFixtureTearDown].</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///		[TestFixtureSetUp]
        ///		public void Test_Group_Setup()
        ///		{
        ///         // The test case is an embeded resource...
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_Group_Setup.xml", BizUnit.TestGroupPhase.TestGroupSetup);
        ///			bizUnit.RunTest();
        ///		}
        ///		
        ///		...
        ///		
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(Stream configStream, TestGroupPhase testGroupPhase)
        {
            ArgumentValidation.CheckForNullReference(configStream, "configStream");
            ArgumentValidation.CheckForNullReference(testGroupPhase, "testGroupPhase");

            LoadXmlFromStreamAndInit(configStream, testGroupPhase, null);
        }

        /// <summary>
        /// BizUnit constructor for the setup and teardown of a test group.
        /// </summary>
        /// <param name="configStream">The path of the test case file, maybe a relavtive path.</param>
        /// <param name="testGroupPhase">The test group phase (TestGroupPhase.TestGroupSetup|TestGroupPhase.TestGroupTearDown). This 
        /// constructor is used during the initialization or termination of a group of test cases, for example when using the NUnit attributes: [TestFixtureSetUp] or [TestFixtureTearDown].</param>
        /// <param name="ctx">The BizUnit context object may be flowed from an previous test case.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///     Context ctx;
        /// 
        ///		[TestFixtureSetUp]
        ///		public void Test_Group_Setup()
        ///		{
        ///         // The test case is an embeded resource...
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_Group_Setup.xml", BizUnit.TestGroupPhase.TestGroupSetup, ctx);
        ///			bizUnit.RunTest();
        ///		}
        ///		
        ///		...
        ///		
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public BizUnit(Stream configStream, TestGroupPhase testGroupPhase, Context ctx)
        {
            ArgumentValidation.CheckForNullReference(configStream, "configStream");
            ArgumentValidation.CheckForNullReference(testGroupPhase, "testGroupPhase");
            ArgumentValidation.CheckForNullReference(ctx, "ctx");

            LoadXmlFromStreamAndInit(configStream, testGroupPhase, ctx);
        }

        /// <summary>
        /// Gets the BizUnit context for the current test.
        /// </summary>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///     Context ctx;
        /// 
        ///		[TestFixtureSetUp]
        ///		public void Test_Group_Setup()
        ///		{
        ///         // The test case is an embeded resource...
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_Group_Setup.xml", BizUnit.TestGroupPhase.TestGroupSetup);
        ///         this.ctx = bizUnit.Ctx;
        /// 
        ///			bizUnit.RunTest();
        ///		}
        ///		
        ///		...
        ///		
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public Context Ctx
        {
            get
            {
                return this.context;
            }
        }

        private void LoadXmlFromFileAndInit(
            string configFile, TestGroupPhase phase, Context ctx)
        {
            XmlDocument testConfig = new XmlDocument();

            try
            {
                testConfig.Load(configFile);
            }
            catch (Exception ex)
            {
                logger.LogError("Failed to load the step configuration file: \"{0}\", exception: {1}", configFile, ex.ToString());
                throw;
            }

            Init(testConfig, phase, ctx);
        }

        private void LoadXmlFromStreamAndInit(
            Stream configStream, TestGroupPhase phase, Context ctx)
        {
            XmlDocument testConfig = new XmlDocument();

            try
            {
                StreamReader sr = new StreamReader(configStream);
                testConfig.LoadXml(sr.ReadToEnd());
            }
            catch (Exception ex)
            {
                logger.LogError("Failed to load the step configuration stream, exception: {0}", ex.ToString());
                throw;
            }

            Init(testConfig, phase, ctx);
        }

        private void Init(
            XmlDocument testConfig, TestGroupPhase phase, Context ctx)
        {
            Logger log = new Logger();

            if (null != ctx)
            {
                this.context = ctx;
                this.context.Initialize(this);
            }
            else
            {
                this.context = new Context(this, log);
            }

            this.testGroupPhase = phase;

            // Get test name...
            XmlNode nameNode = testConfig.SelectSingleNode("/TestCase/@testName");
            if (null != nameNode)
            {
                this.testName = nameNode.Value;
                this.context.Add("BizUnitTestCaseName", this.testName, true);
            }

            if (phase == TestGroupPhase.Unknown)
            {
                LogTestStart(log);
            }
            else if (phase == TestGroupPhase.TestGroupSetup)
            {
                LogTestGroupSetup(log);
            }
            else if (phase == TestGroupPhase.TestGroupTearDown)
            {
                LogTestGroupTearDown(log);
            }

            // Get test setup / execution / teardown steps
            this.setupSteps = testConfig.SelectNodes("/TestCase/TestSetup/*");
            this.executeSteps = testConfig.SelectNodes("/TestCase/TestExecution/*");
            this.teardownSteps = testConfig.SelectNodes("/TestCase/TestCleanup/*");

            this.logger = log;
        }

        private void LogTestStart(Logger log)
        {
            log.WriteLine(" ");
            log.WriteLine(new string('-', 79));
            log.WriteLine("                                   S T A R T");
            log.WriteLine(" ");
            string userName = GetUserName();
            log.WriteLine(string.Format("Test: {0} started @ {1} by {2}", this.testName, GetNow(), userName));
            log.WriteLine(new string('-', 79));
        }

        private void LogTestGroupSetup(Logger log)
        {
            log.WriteLine(" ");
            log.WriteLine(new string('-', 79));
            log.WriteLine("                        T E S T   G R O U P   S E T U P");
            log.WriteLine(" ");
            string userName = GetUserName();
            log.WriteLine(string.Format("Test Group Setup: {0} started @ {1} by {2}", this.testName, GetNow(), userName));
            log.WriteLine(new string('-', 79));
        }

        private void LogTestGroupTearDown(Logger log)
        {
            log.WriteLine(" ");
            log.WriteLine(new string('-', 79));
            log.WriteLine("                   T E S T   G R O U P   T E A R D O W N");
            log.WriteLine(" ");
            log.WriteLine(string.Format("Test Group Tear Down: {0} completed @ {1}", this.testName, GetNow()));
            log.WriteLine(new string('-', 79));
        }

        /// <summary>
        /// Executes a test case.
        /// </summary>
        /// <returns>Returns void</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to create and call BizUnit:
        /// 
        /// <code escaped="true">
        ///	namespace WoodgroveBank.BVTs
        ///	{
        ///	using System;
        ///	using NUnit.Framework;
        ///	using Microsoft.Services.BizTalkApplicationFramework.BizUnit;
        ///
        /// // This is an example of calling BizUnit from NUnit...
        ///	[TestFixture]
        ///	public class SmokeTests
        ///	{
        ///		[Test]
        ///		public void Test_01_Adapter_MSMQ()
        ///		{
        ///			BizUnit bizUnit = new BizUnit(@".\TestCases\Test_01_Adapter_MSMQ.xml");
        ///			bizUnit.RunTest();
        ///		}
        ///	}		
        ///	</code>
        ///	
        ///	</remarks>
        public void RunTest()
        {
            Setup();
            Execute();
            TearDown();

            this.logger.Close();
        }

        private void Setup()
        {
            this.logger.TestSetUp(this.testName);

            ExecuteSteps(this.setupSteps);
        }

        private void Execute()
        {
            this.logger.TestExecute(this.testName);

            try
            {
                ExecuteSteps(this.executeSteps);
            }
            catch (Exception e)
            {
                // If we caught an exception on the main test execution, save it, perform cleanup,
                // then throw the exception...
                this.executionException = e;
            }
        }

        private void TearDown()
        {
            this.logger.TestTearDown(this.testName);

            try
            {
                ExecuteSteps(this.teardownSteps);
            }
            catch (Exception e)
            {
                this.logger.LogException(e);
                this.logger.WriteLine(new string('-', 79));
                this.logger.WriteLine(string.Format("Test: {0} ended @ {1}", this.testName, GetNow()));
                this.logger.WriteLine("");
                this.logger.WriteLine("                             ****** F A I L ******");
                this.logger.WriteLine(new string('-', 79));

                if (null != this.executionException)
                {
                    throw this.executionException;
                }
                else
                {
                    throw;
                }
            }

            this.logger.WriteLine(new string('-', 79));

            if (TestGroupPhase.Unknown != this.testGroupPhase)
            {
                if (this.testGroupPhase == TestGroupPhase.TestGroupSetup)
                {
                    if (null != this.executionException)
                    {
                        this.logger.WriteLine(string.Format("Test Group Setup completed @ {0}", GetNow()));
                        this.logger.WriteLine("          ****** T E S T   G R O U P   S E T U P   F A I L E D ******");
                    }
                    else
                    {
                        this.logger.WriteLine(string.Format("Test Group Setup completed @ {0}", GetNow()));
                        this.logger.WriteLine("                  T E S T   G R O U P   S E T U P   P A S S");
                    }
                }
                else
                {
                    if (null != this.executionException)
                    {
                        this.logger.WriteLine(string.Format("Test Group Tear Down completed @ {0}", GetNow()));
                        this.logger.WriteLine("       ****** T E S T   G R O U P   T E A R D O W N   F A I L E D ******");
                    }
                    else
                    {
                        this.logger.WriteLine(string.Format("Test Group Tear Down completed @ {0}", GetNow()));
                        this.logger.WriteLine("              T E S T   G R O U P   T E A R D O W N   P A S S");
                    }
                }
            }
            else
            {
                this.logger.WriteLine(string.Format("Test: {0} ended @ {1}", this.testName, GetNow()));

                if (null != this.executionException)
                {
                    this.logger.WriteLine("                             ****** F A I L ******");
                }
                else
                {
                    this.logger.WriteLine("                                    P A S S");
                }
            }

            this.logger.WriteLine("");
            this.logger.WriteLine(new string('-', 79));

            if (null != this.executionException)
            {
                throw this.executionException;
            }
        }

        private void ExecuteSteps(XmlNodeList steps)
        {
            if (null == steps)
            {
                return;
            }

            foreach (XmlNode stepConfig in steps)
            {
                bool runConcurrently = false;
                bool failOnError = true;
                XmlNode assemblyPath = stepConfig.SelectSingleNode("@assemblyPath");
                XmlNode typeName = stepConfig.SelectSingleNode("@typeName");
                XmlNode runConcurrentlyNode = stepConfig.SelectSingleNode("@runConcurrently");
                XmlNode failOnErrorNode = stepConfig.SelectSingleNode("@failOnError");

                if (null != runConcurrentlyNode)
                {
                    runConcurrently = Convert.ToBoolean(runConcurrentlyNode.Value);
                }

                if (null != failOnErrorNode)
                {
                    failOnError = Convert.ToBoolean(failOnErrorNode.Value);
                }

                object obj = CreateStep(typeName.Value, assemblyPath.Value);
                ITestStep step = obj as ITestStep;

                if (null == step)
                {
                    this.logger.WriteLine("The test step could not be created, check the test step type and assembly path are correct, type: {0}, assembly path: {0}", typeName.Value, assemblyPath.Value);
                    throw new ApplicationException(string.Format("The test step could not be created, check the test step type and assembly path are correct, type: {0}, assembly path: {1}", typeName.Value, assemblyPath.Value));
                }

                try
                {
                    // Should this step be executed concurrently?
                    if (runConcurrently)
                    {
                        this.logger.WriteLine("");
                        this.logger.WriteLine(string.Format("Step: {0} started  c o n c u r r e n t l y  @ {1}, failOnError = {2}", typeName.Value, GetNow(), failOnError));
                        Interlocked.Increment(ref this.inflightQueueDepth);
                        ThreadPool.QueueUserWorkItem(new WaitCallback(this.WorkerThreadThunk), new ConcurrentTestStepWrapper(step, stepConfig, context, typeName.Value, failOnError));
                    }
                    else
                    {
                        this.logger.WriteLine("");
                        this.logger.WriteLine(string.Format("Step: {0} started @ {1}, failOnError = {2}", typeName.Value, GetNow(), failOnError));

                        step.Execute(stepConfig, context);
                    }
                }
                catch (Exception e)
                {
                    this.logger.LogException(e);

                    if (failOnError)
                    {
                        throw;
                    }
                }

                if (!runConcurrently)
                {
                    this.logger.WriteLine(string.Format("Step: {0} ended @ {1}", typeName.Value, GetNow()));
                }

                FlushConcurrentQueue(false);
            }

            FlushConcurrentQueue(true);
        }

        private void FlushConcurrentQueue(bool waitingToFinish)
        {
            if (waitingToFinish && this.inflightQueueDepth == 0)
            {
                return;
            }

            while ((this.completedConcurrentSteps.Count > 0) || waitingToFinish)
            {
                object obj = null;

                lock (this.completedConcurrentSteps.SyncRoot)
                {
                    if (this.completedConcurrentSteps.Count > 0)
                    {
                        try
                        {
                            obj = this.completedConcurrentSteps.Dequeue();
                        }
                        catch (Exception)
                        {
                        }
                    }
                }

                if (null != obj)
                {
                    ConcurrentTestStepWrapper step = (ConcurrentTestStepWrapper)obj;
                    string testLog = step.GetLogText();
                    this.logger.WriteLine(testLog);

                    this.logger.WriteLine(string.Format("Step: {0} ended @ {1} (Concurrent Execution Mode defined)", step.Name, GetNow()));

                    // Check to see if the test step failed, if it did throw the exception...
                    if (null != step.FailureException)
                    {
                        Interlocked.Decrement(ref this.inflightQueueDepth);

                        if (step.FailOnError)
                        {
                            throw step.FailureException;
                        }
                    }
                    else
                    {
                        Interlocked.Decrement(ref this.inflightQueueDepth);
                    }
                }

                if (waitingToFinish && (this.inflightQueueDepth > 0))
                {
                    Thread.Sleep(250);
                }
                else if (waitingToFinish && (this.inflightQueueDepth == 0))
                {
                    break;
                }
            }
        }

        private void WorkerThreadThunk(Object stateInfo)
        {
            ConcurrentTestStepWrapper step = (ConcurrentTestStepWrapper)stateInfo;
            step.Execute();

            // This step is completed, add to queue
            lock (this.completedConcurrentSteps.SyncRoot)
            {
                this.completedConcurrentSteps.Enqueue(step);
            }
        }

        static private IValidationStep CreateValidatorStep(string typeName, string assemblyPath)
        {
            return (IValidationStep)CreateStep(typeName, assemblyPath);
        }

        static private IContextLoaderStep CreateContextLoaderStep(string typeName, string assemblyPath)
        {
            return (IContextLoaderStep)CreateStep(typeName, assemblyPath);
        }

        static private object CreateStep(string typeName, string assemblyPath)
        {
            object comp = null;
            Type ty;

            if (assemblyPath != null && assemblyPath.Length != 0)
            {
                Assembly assembly = Assembly.LoadFrom(assemblyPath);
                ty = assembly.GetType(typeName, true, false);
            }
            else
            {
                ty = Type.GetType(typeName);
            }

            if (ty != null)
            {
                comp = Activator.CreateInstance(ty);
            }

            return comp;
        }

        internal void ExecuteValidator(Stream data, XmlNode validatorConfig, Context ctx)
        {
            if (null == validatorConfig)
            {
                return;
            }

            XmlNode assemblyPath = validatorConfig.SelectSingleNode("@assemblyPath");
            XmlNode typeName = validatorConfig.SelectSingleNode("@typeName");

            this.logger.WriteLine("");
            this.logger.WriteLine(string.Format("Validation: {0} started @ {1}", typeName.Value, GetNow()));

            IValidationStep v = CreateValidatorStep(typeName.Value, assemblyPath.Value);
            v.ExecuteValidation(data, validatorConfig, ctx);

            this.logger.WriteLine(string.Format("Validation: {0} ended @ {1}", typeName.Value, GetNow()));
            this.logger.WriteLine("");
        }

        internal void ExecuteContextLoader(Stream data, XmlNode contextConfig, Context ctx)
        {
            if (null == contextConfig)
            {
                return;
            }

            XmlNode assemblyPath = contextConfig.SelectSingleNode("@assemblyPath");
            XmlNode typeName = contextConfig.SelectSingleNode("@typeName");

            this.logger.WriteLine("");
            this.logger.WriteLine(string.Format("ContextLoad: {0} started @ {1}", typeName.Value, GetNow()));

            IContextLoaderStep cd = CreateContextLoaderStep(typeName.Value, assemblyPath.Value);
            cd.ExecuteContextLoader(data, contextConfig, ctx);

            this.logger.WriteLine(string.Format("ContextLoad: {0} ended @ {1}", typeName.Value, GetNow()));
            this.logger.WriteLine("");
        }

        internal static string GetNow()
        {
            return DateTime.Now.ToString("HH:mm:ss.fff dd/MM/yyyy");
        }

        internal static string GetUserName()
        {
            string usersDomain = Environment.UserDomainName;
            string usersName = Environment.UserName;

            return usersDomain + "\\" + usersName;
        }
    }
}
